<?php

namespace gyh\GyhReadsheet\Reader\Xls;

use gyh\GyhReadsheet\Reader\Xls;
use gyh\GyhReadsheet\Style\Border as StyleBorder;
use gyh\GyhReadsheet\Style\Fill;

abstract class Coordinate
{
    const DEFAULT_RANGE = 'A1:A1';

    public static function coordinateFromString($pCoordinateString)
    {
        if (preg_match('/^([$]?[A-Z]{1,3})([$]?\\d{1,7})$/', $pCoordinateString, $matches)) {
            return [$matches[1], $matches[2]];
        } elseif (self::coordinateIsRange($pCoordinateString)) {
            throw new Exception('Cell coordinate string can not be a range of cells');
        } elseif ($pCoordinateString == '') {
            throw new Exception('Cell coordinate can not be zero-length string');
        }

        throw new Exception('Invalid cell coordinate ' . $pCoordinateString);
    }

    public static function coordinateIsRange($coord)
    {
        return (strpos($coord, ':') !== false) || (strpos($coord, ',') !== false);
    }

    public static function absoluteReference($pCoordinateString)
    {
        if (self::coordinateIsRange($pCoordinateString)) {
            throw new Exception('Cell coordinate string can not be a range of cells');
        }

        list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
        if ($worksheet > '') {
            $worksheet .= '!';
        }

        if (ctype_digit($pCoordinateString)) {
            return $worksheet . '$' . $pCoordinateString;
        } elseif (ctype_alpha($pCoordinateString)) {
            return $worksheet . '$' . strtoupper($pCoordinateString);
        }

        return $worksheet . self::absoluteCoordinate($pCoordinateString);
    }

    public static function absoluteCoordinate($pCoordinateString)
    {
        if (self::coordinateIsRange($pCoordinateString)) {
            throw new Exception('Cell coordinate string can not be a range of cells');
        }

        list($worksheet, $pCoordinateString) = Worksheet::extractSheetTitle($pCoordinateString, true);
        if ($worksheet > '') {
            $worksheet .= '!';
        }

        list($column, $row) = self::coordinateFromString($pCoordinateString);
        $column = ltrim($column, '$');
        $row = ltrim($row, '$');

        return $worksheet . '$' . $column . '$' . $row;
    }

    public static function splitRange($pRange)
    {
        if (empty($pRange)) {
            $pRange = self::DEFAULT_RANGE;
        }

        $exploded = explode(',', $pRange);
        $counter = count($exploded);
        for ($i = 0; $i < $counter; ++$i) {
            $exploded[$i] = explode(':', $exploded[$i]);
        }

        return $exploded;
    }

    public static function buildRange(array $pRange)
    {
        if (empty($pRange) || !is_array($pRange[0])) {
            throw new Exception('Range does not contain any information');
        }

        $imploded = [];
        $counter = count($pRange);
        for ($i = 0; $i < $counter; ++$i) {
            $pRange[$i] = implode(':', $pRange[$i]);
        }
        $imploded = implode(',', $pRange);

        return $imploded;
    }

    public static function rangeBoundaries($pRange)
    {
        if (empty($pRange)) {
            $pRange = self::DEFAULT_RANGE;
        }

        $pRange = strtoupper($pRange);

        if (strpos($pRange, ':') === false) {
            $rangeA = $rangeB = $pRange;
        } else {
            list($rangeA, $rangeB) = explode(':', $pRange);
        }

        $rangeStart = self::coordinateFromString($rangeA);
        $rangeEnd = self::coordinateFromString($rangeB);

        $rangeStart[0] = self::columnIndexFromString($rangeStart[0]);
        $rangeEnd[0] = self::columnIndexFromString($rangeEnd[0]);

        return [$rangeStart, $rangeEnd];
    }

    public static function rangeDimension($pRange)
    {
        list($rangeStart, $rangeEnd) = self::rangeBoundaries($pRange);

        return [($rangeEnd[0] - $rangeStart[0] + 1), ($rangeEnd[1] - $rangeStart[1] + 1)];
    }

    public static function getRangeBoundaries($pRange)
    {
        if (empty($pRange)) {
            $pRange = self::DEFAULT_RANGE;
        }

        $pRange = strtoupper($pRange);

        if (strpos($pRange, ':') === false) {
            $rangeA = $rangeB = $pRange;
        } else {
            list($rangeA, $rangeB) = explode(':', $pRange);
        }

        return [self::coordinateFromString($rangeA), self::coordinateFromString($rangeB)];
    }

    public static function columnIndexFromString($pString)
    {
        static $indexCache = [];

        if (isset($indexCache[$pString])) {
            return $indexCache[$pString];
        }
        static $columnLookup = [
            'A' => 1, 'B' => 2, 'C' => 3, 'D' => 4, 'E' => 5, 'F' => 6, 'G' => 7, 'H' => 8, 'I' => 9, 'J' => 10, 'K' => 11, 'L' => 12, 'M' => 13,
            'N' => 14, 'O' => 15, 'P' => 16, 'Q' => 17, 'R' => 18, 'S' => 19, 'T' => 20, 'U' => 21, 'V' => 22, 'W' => 23, 'X' => 24, 'Y' => 25, 'Z' => 26,
            'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6, 'g' => 7, 'h' => 8, 'i' => 9, 'j' => 10, 'k' => 11, 'l' => 12, 'm' => 13,
            'n' => 14, 'o' => 15, 'p' => 16, 'q' => 17, 'r' => 18, 's' => 19, 't' => 20, 'u' => 21, 'v' => 22, 'w' => 23, 'x' => 24, 'y' => 25, 'z' => 26,
        ];

        if (isset($pString[0])) {
            if (!isset($pString[1])) {
                $indexCache[$pString] = $columnLookup[$pString];

                return $indexCache[$pString];
            } elseif (!isset($pString[2])) {
                $indexCache[$pString] = $columnLookup[$pString[0]] * 26 + $columnLookup[$pString[1]];

                return $indexCache[$pString];
            } elseif (!isset($pString[3])) {
                $indexCache[$pString] = $columnLookup[$pString[0]] * 676 + $columnLookup[$pString[1]] * 26 + $columnLookup[$pString[2]];

                return $indexCache[$pString];
            }
        }

        throw new Exception('Column string index can not be ' . ((isset($pString[0])) ? 'longer than 3 characters' : 'empty'));
    }

    public static function stringFromColumnIndex($columnIndex)
    {
        static $indexCache = [];

        if (!isset($indexCache[$columnIndex])) {
            $indexValue = $columnIndex;
            $base26 = null;
            do {
                $characterValue = ($indexValue % 26) ?: 26;
                $indexValue = ($indexValue - $characterValue) / 26;
                $base26 = chr($characterValue + 64) . ($base26 ?: '');
            } while ($indexValue > 0);
            $indexCache[$columnIndex] = $base26;
        }

        return $indexCache[$columnIndex];
    }

    public static function extractAllCellReferencesInRange($pRange)
    {
        $returnValue = [];

        $cellBlocks = self::getCellBlocksFromRangeString($pRange);
        foreach ($cellBlocks as $cellBlock) {
            $returnValue = array_merge($returnValue, self::getReferencesForCellBlock($cellBlock));
        }

        $sortKeys = [];
        foreach (array_unique($returnValue) as $coord) {
            $column = '';
            $row = 0;

            sscanf($coord, '%[A-Z]%d', $column, $row);
            $sortKeys[sprintf('%3s%09d', $column, $row)] = $coord;
        }
        ksort($sortKeys);

        return array_values($sortKeys);
    }

    private static function getReferencesForCellBlock($cellBlock)
    {
        $returnValue = [];

        if (!self::coordinateIsRange($cellBlock)) {
            return (array) $cellBlock;
        }

        $ranges = self::splitRange($cellBlock);
        foreach ($ranges as $range) {
            if (!isset($range[1])) {
                $returnValue[] = $range[0];

                continue;
            }

            list($rangeStart, $rangeEnd) = $range;
            list($startColumn, $startRow) = self::coordinateFromString($rangeStart);
            list($endColumn, $endRow) = self::coordinateFromString($rangeEnd);
            $startColumnIndex = self::columnIndexFromString($startColumn);
            $endColumnIndex = self::columnIndexFromString($endColumn);
            ++$endColumnIndex;

            $currentColumnIndex = $startColumnIndex;
            $currentRow = $startRow;

            self::validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow);

            while ($currentColumnIndex < $endColumnIndex) {
                while ($currentRow <= $endRow) {
                    $returnValue[] = self::stringFromColumnIndex($currentColumnIndex) . $currentRow;
                    ++$currentRow;
                }
                ++$currentColumnIndex;
                $currentRow = $startRow;
            }
        }

        return $returnValue;
    }

    public static function mergeRangesInCollection(array $pCoordCollection)
    {
        $hashedValues = [];
        $mergedCoordCollection = [];

        foreach ($pCoordCollection as $coord => $value) {
            if (self::coordinateIsRange($coord)) {
                $mergedCoordCollection[$coord] = $value;

                continue;
            }

            list($column, $row) = self::coordinateFromString($coord);
            $row = (int) (ltrim($row, '$'));
            $hashCode = $column . '-' . (is_object($value) ? $value->getHashCode() : $value);

            if (!isset($hashedValues[$hashCode])) {
                $hashedValues[$hashCode] = (object) [
                    'value' => $value,
                    'col' => $column,
                    'rows' => [$row],
                ];
            } else {
                $hashedValues[$hashCode]->rows[] = $row;
            }
        }

        ksort($hashedValues);

        foreach ($hashedValues as $hashedValue) {
            sort($hashedValue->rows);
            $rowStart = null;
            $rowEnd = null;
            $ranges = [];

            foreach ($hashedValue->rows as $row) {
                if ($rowStart === null) {
                    $rowStart = $row;
                    $rowEnd = $row;
                } elseif ($rowEnd === $row - 1) {
                    $rowEnd = $row;
                } else {
                    if ($rowStart == $rowEnd) {
                        $ranges[] = $hashedValue->col . $rowStart;
                    } else {
                        $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
                    }

                    $rowStart = $row;
                    $rowEnd = $row;
                }
            }

            if ($rowStart !== null) {
                if ($rowStart == $rowEnd) {
                    $ranges[] = $hashedValue->col . $rowStart;
                } else {
                    $ranges[] = $hashedValue->col . $rowStart . ':' . $hashedValue->col . $rowEnd;
                }
            }

            foreach ($ranges as $range) {
                $mergedCoordCollection[$range] = $hashedValue->value;
            }
        }

        return $mergedCoordCollection;
    }

    private static function getCellBlocksFromRangeString($pRange)
    {
        return explode(' ', str_replace('$', '', strtoupper($pRange)));
    }

    private static function validateRange($cellBlock, $startColumnIndex, $endColumnIndex, $currentRow, $endRow)
    {
        if ($startColumnIndex >= $endColumnIndex || $currentRow > $endRow) {
            throw new Exception('Invalid range: "' . $cellBlock . '"');
        }
    }
}


class SpContainer
{
    private $parent;

    private $spgr = false;

    private $spType;

    private $spFlag;

    private $spId;

    private $OPT;

    private $startCoordinates;

    private $startOffsetX;

    private $startOffsetY;

    private $endCoordinates;

    private $endOffsetX;

    private $endOffsetY;

    public function setParent($parent)
    {
        $this->parent = $parent;
    }

    public function getParent()
    {
        return $this->parent;
    }

    public function setSpgr($value)
    {
        $this->spgr = $value;
    }

    public function getSpgr()
    {
        return $this->spgr;
    }

    public function setSpType($value)
    {
        $this->spType = $value;
    }

    public function getSpType()
    {
        return $this->spType;
    }

    public function setSpFlag($value)
    {
        $this->spFlag = $value;
    }

    public function getSpFlag()
    {
        return $this->spFlag;
    }

    public function setSpId($value)
    {
        $this->spId = $value;
    }

    public function getSpId()
    {
        return $this->spId;
    }

    public function setOPT($property, $value)
    {
        $this->OPT[$property] = $value;
    }

    public function getOPT($property)
    {
        if (isset($this->OPT[$property])) {
            return $this->OPT[$property];
        }

        return null;
    }

    public function getOPTCollection()
    {
        return $this->OPT;
    }

    public function setStartCoordinates($value)
    {
        $this->startCoordinates = $value;
    }

    public function getStartCoordinates()
    {
        return $this->startCoordinates;
    }

    public function setStartOffsetX($startOffsetX)
    {
        $this->startOffsetX = $startOffsetX;
    }

    public function getStartOffsetX()
    {
        return $this->startOffsetX;
    }

    public function setStartOffsetY($startOffsetY)
    {
        $this->startOffsetY = $startOffsetY;
    }

    public function getStartOffsetY()
    {
        return $this->startOffsetY;
    }

    public function setEndCoordinates($value)
    {
        $this->endCoordinates = $value;
    }

    public function getEndCoordinates()
    {
        return $this->endCoordinates;
    }

    public function setEndOffsetX($endOffsetX)
    {
        $this->endOffsetX = $endOffsetX;
    }

    public function getEndOffsetX()
    {
        return $this->endOffsetX;
    }

    public function setEndOffsetY($endOffsetY)
    {
        $this->endOffsetY = $endOffsetY;
    }

    public function getEndOffsetY()
    {
        return $this->endOffsetY;
    }

    public function getNestingLevel()
    {
        $nestingLevel = 0;

        $parent = $this->getParent();
        while ($parent instanceof SpgrContainer) {
            ++$nestingLevel;
            $parent = $parent->getParent();
        }

        return $nestingLevel;
    }
}

class SpgrContainer
{
    private $parent;

    private $children = [];

    public function setParent($parent)
    {
        $this->parent = $parent;
    }

    public function getParent()
    {
        return $this->parent;
    }

    public function addChild($child)
    {
        $this->children[] = $child;
        $child->setParent($this);
    }

    public function getChildren()
    {
        return $this->children;
    }

    public function getAllSpContainers()
    {
        $allSpContainers = [];

        foreach ($this->children as $child) {
            if ($child instanceof self) {
                $allSpContainers = array_merge($allSpContainers, $child->getAllSpContainers());
            } else {
                $allSpContainers[] = $child;
            }
        }

        return $allSpContainers;
    }
}

class DgContainer
{
    private $dgId;

    private $lastSpId;

    private $spgrContainer;

    public function getDgId()
    {
        return $this->dgId;
    }

    public function setDgId($value)
    {
        $this->dgId = $value;
    }

    public function getLastSpId()
    {
        return $this->lastSpId;
    }

    public function setLastSpId($value)
    {
        $this->lastSpId = $value;
    }

    public function getSpgrContainer()
    {
        return $this->spgrContainer;
    }

    public function setSpgrContainer($spgrContainer)
    {
        return $this->spgrContainer = $spgrContainer;
    }
}

class DggContainer
{
    private $spIdMax;

    private $cDgSaved;

    private $cSpSaved;

    private $bstoreContainer;

    private $OPT = [];

    private $IDCLs = [];

    public function getSpIdMax()
    {
        return $this->spIdMax;
    }

    public function setSpIdMax($value)
    {
        $this->spIdMax = $value;
    }

    public function getCDgSaved()
    {
        return $this->cDgSaved;
    }

    public function setCDgSaved($value)
    {
        $this->cDgSaved = $value;
    }

    public function getCSpSaved()
    {
        return $this->cSpSaved;
    }

    public function setCSpSaved($value)
    {
        $this->cSpSaved = $value;
    }

    public function getBstoreContainer()
    {
        return $this->bstoreContainer;
    }

    public function setBstoreContainer($bstoreContainer)
    {
        $this->bstoreContainer = $bstoreContainer;
    }

    public function setOPT($property, $value)
    {
        $this->OPT[$property] = $value;
    }

    public function getOPT($property)
    {
        if (isset($this->OPT[$property])) {
            return $this->OPT[$property];
        }

        return null;
    }

    public function getIDCLs()
    {
        return $this->IDCLs;
    }

    public function setIDCLs($pValue)
    {
        $this->IDCLs = $pValue;
    }
}


class Color
{
    public static function map($color, $palette, $version)
    {
        if ($color <= 0x07 || $color >= 0x40) {
            return BuiltIn::lookup($color);
        } elseif (isset($palette, $palette[$color - 8])) {
            return $palette[$color - 8];
        }

        if ($version == Xls::XLS_BIFF8) {
            return BIFF8::lookup($color);
        }

        return BIFF5::lookup($color);
    }
}

class Border
{
    protected static $map = [
        0x00 => StyleBorder::BORDER_NONE,
        0x01 => StyleBorder::BORDER_THIN,
        0x02 => StyleBorder::BORDER_MEDIUM,
        0x03 => StyleBorder::BORDER_DASHED,
        0x04 => StyleBorder::BORDER_DOTTED,
        0x05 => StyleBorder::BORDER_THICK,
        0x06 => StyleBorder::BORDER_DOUBLE,
        0x07 => StyleBorder::BORDER_HAIR,
        0x08 => StyleBorder::BORDER_MEDIUMDASHED,
        0x09 => StyleBorder::BORDER_DASHDOT,
        0x0A => StyleBorder::BORDER_MEDIUMDASHDOT,
        0x0B => StyleBorder::BORDER_DASHDOTDOT,
        0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT,
        0x0D => StyleBorder::BORDER_SLANTDASHDOT,
    ];

    public static function lookup($index)
    {
        if (isset(self::$map[$index])) {
            return self::$map[$index];
        }

        return StyleBorder::BORDER_NONE;
    }
}

class FillPattern
{
    protected static $map = [
        0x00 => Fill::FILL_NONE,
        0x01 => Fill::FILL_SOLID,
        0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
        0x03 => Fill::FILL_PATTERN_DARKGRAY,
        0x04 => Fill::FILL_PATTERN_LIGHTGRAY,
        0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL,
        0x06 => Fill::FILL_PATTERN_DARKVERTICAL,
        0x07 => Fill::FILL_PATTERN_DARKDOWN,
        0x08 => Fill::FILL_PATTERN_DARKUP,
        0x09 => Fill::FILL_PATTERN_DARKGRID,
        0x0A => Fill::FILL_PATTERN_DARKTRELLIS,
        0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
        0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL,
        0x0D => Fill::FILL_PATTERN_LIGHTDOWN,
        0x0E => Fill::FILL_PATTERN_LIGHTUP,
        0x0F => Fill::FILL_PATTERN_LIGHTGRID,
        0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS,
        0x11 => Fill::FILL_PATTERN_GRAY125,
        0x12 => Fill::FILL_PATTERN_GRAY0625,
    ];

    public static function lookup($index)
    {
        if (isset(self::$map[$index])) {
            return self::$map[$index];
        }

        return Fill::FILL_NONE;
    }
}


class BIFF8
{
    protected static $map = [
        0x08 => '000000',
        0x09 => 'FFFFFF',
        0x0A => 'FF0000',
        0x0B => '00FF00',
        0x0C => '0000FF',
        0x0D => 'FFFF00',
        0x0E => 'FF00FF',
        0x0F => '00FFFF',
        0x10 => '800000',
        0x11 => '008000',
        0x12 => '000080',
        0x13 => '808000',
        0x14 => '800080',
        0x15 => '008080',
        0x16 => 'C0C0C0',
        0x17 => '808080',
        0x18 => '9999FF',
        0x19 => '993366',
        0x1A => 'FFFFCC',
        0x1B => 'CCFFFF',
        0x1C => '660066',
        0x1D => 'FF8080',
        0x1E => '0066CC',
        0x1F => 'CCCCFF',
        0x20 => '000080',
        0x21 => 'FF00FF',
        0x22 => 'FFFF00',
        0x23 => '00FFFF',
        0x24 => '800080',
        0x25 => '800000',
        0x26 => '008080',
        0x27 => '0000FF',
        0x28 => '00CCFF',
        0x29 => 'CCFFFF',
        0x2A => 'CCFFCC',
        0x2B => 'FFFF99',
        0x2C => '99CCFF',
        0x2D => 'FF99CC',
        0x2E => 'CC99FF',
        0x2F => 'FFCC99',
        0x30 => '3366FF',
        0x31 => '33CCCC',
        0x32 => '99CC00',
        0x33 => 'FFCC00',
        0x34 => 'FF9900',
        0x35 => 'FF6600',
        0x36 => '666699',
        0x37 => '969696',
        0x38 => '003366',
        0x39 => '339966',
        0x3A => '003300',
        0x3B => '333300',
        0x3C => '993300',
        0x3D => '993366',
        0x3E => '333399',
        0x3F => '333333',
    ];

    public static function lookup($color)
    {
        if (isset(self::$map[$color])) {
            return ['rgb' => self::$map[$color]];
        }

        return ['rgb' => '000000'];
    }
}


class BIFF5
{
    protected static $map = [
        0x08 => '000000',
        0x09 => 'FFFFFF',
        0x0A => 'FF0000',
        0x0B => '00FF00',
        0x0C => '0000FF',
        0x0D => 'FFFF00',
        0x0E => 'FF00FF',
        0x0F => '00FFFF',
        0x10 => '800000',
        0x11 => '008000',
        0x12 => '000080',
        0x13 => '808000',
        0x14 => '800080',
        0x15 => '008080',
        0x16 => 'C0C0C0',
        0x17 => '808080',
        0x18 => '8080FF',
        0x19 => '802060',
        0x1A => 'FFFFC0',
        0x1B => 'A0E0F0',
        0x1C => '600080',
        0x1D => 'FF8080',
        0x1E => '0080C0',
        0x1F => 'C0C0FF',
        0x20 => '000080',
        0x21 => 'FF00FF',
        0x22 => 'FFFF00',
        0x23 => '00FFFF',
        0x24 => '800080',
        0x25 => '800000',
        0x26 => '008080',
        0x27 => '0000FF',
        0x28 => '00CFFF',
        0x29 => '69FFFF',
        0x2A => 'E0FFE0',
        0x2B => 'FFFF80',
        0x2C => 'A6CAF0',
        0x2D => 'DD9CB3',
        0x2E => 'B38FEE',
        0x2F => 'E3E3E3',
        0x30 => '2A6FF9',
        0x31 => '3FB8CD',
        0x32 => '488436',
        0x33 => '958C41',
        0x34 => '8E5E42',
        0x35 => 'A0627A',
        0x36 => '624FAC',
        0x37 => '969696',
        0x38 => '1D2FBE',
        0x39 => '286676',
        0x3A => '004500',
        0x3B => '453E01',
        0x3C => '6A2813',
        0x3D => '85396A',
        0x3E => '4A3285',
        0x3F => '424242',
    ];

    public static function lookup($color)
    {
        if (isset(self::$map[$color])) {
            return ['rgb' => self::$map[$color]];
        }

        return ['rgb' => '000000'];
    }
}


class BuiltIn
{
    protected static $map = [
        0x00 => '000000',
        0x01 => 'FFFFFF',
        0x02 => 'FF0000',
        0x03 => '00FF00',
        0x04 => '0000FF',
        0x05 => 'FFFF00',
        0x06 => 'FF00FF',
        0x07 => '00FFFF',
        0x40 => '000000', // system window text color
        0x41 => 'FFFFFF', // system window background color
    ];

    public static function lookup($color)
    {
        if (isset(self::$map[$color])) {
            return ['rgb' => self::$map[$color]];
        }

        return ['rgb' => '000000'];
    }
}



class Escher
{
    const DGGCONTAINER = 0xF000;
    const BSTORECONTAINER = 0xF001;
    const DGCONTAINER = 0xF002;
    const SPGRCONTAINER = 0xF003;
    const SPCONTAINER = 0xF004;
    const DGG = 0xF006;
    const BSE = 0xF007;
    const DG = 0xF008;
    const SPGR = 0xF009;
    const SP = 0xF00A;
    const OPT = 0xF00B;
    const CLIENTTEXTBOX = 0xF00D;
    const CLIENTANCHOR = 0xF010;
    const CLIENTDATA = 0xF011;
    const BLIPJPEG = 0xF01D;
    const BLIPPNG = 0xF01E;
    const SPLITMENUCOLORS = 0xF11E;
    const TERTIARYOPT = 0xF122;

    private $data;

    private $dataSize;

    private $pos;

    private $object;

    public function __construct($object)
    {
        $this->object = $object;
    }

    public function load($data)
    {
        $this->data = $data;

        $this->dataSize = strlen($this->data);

        $this->pos = 0;

        while ($this->pos < $this->dataSize) {
            $fbt = Xls::getUInt2d($this->data, $this->pos + 2);

            switch ($fbt) {
                case self::DGGCONTAINER:
                    $this->readDggContainer();

                    break;
                case self::DGG:
                    $this->readDgg();

                    break;
                case self::BSTORECONTAINER:
                    $this->readBstoreContainer();

                    break;
                case self::BSE:
                    $this->readBSE();

                    break;
                case self::BLIPJPEG:
                    $this->readBlipJPEG();

                    break;
                case self::BLIPPNG:
                    $this->readBlipPNG();

                    break;
                case self::OPT:
                    $this->readOPT();

                    break;
                case self::TERTIARYOPT:
                    $this->readTertiaryOPT();

                    break;
                case self::SPLITMENUCOLORS:
                    $this->readSplitMenuColors();

                    break;
                case self::DGCONTAINER:
                    $this->readDgContainer();

                    break;
                case self::DG:
                    $this->readDg();

                    break;
                case self::SPGRCONTAINER:
                    $this->readSpgrContainer();

                    break;
                case self::SPCONTAINER:
                    $this->readSpContainer();

                    break;
                case self::SPGR:
                    $this->readSpgr();

                    break;
                case self::SP:
                    $this->readSp();

                    break;
                case self::CLIENTTEXTBOX:
                    $this->readClientTextbox();

                    break;
                case self::CLIENTANCHOR:
                    $this->readClientAnchor();

                    break;
                case self::CLIENTDATA:
                    $this->readClientData();

                    break;
                default:
                    $this->readDefault();

                    break;
            }
        }

        return $this->object;
    }

    private function readDefault()
    {
        $verInstance = Xls::getUInt2d($this->data, $this->pos);

        $fbt = Xls::getUInt2d($this->data, $this->pos + 2);

        $recVer = (0x000F & $verInstance) >> 0;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readDggContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $dggContainer = new DggContainer();
        $this->object->setDggContainer($dggContainer);
        $reader = new self($dggContainer);
        $reader->load($recordData);
    }

    private function readDgg()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readBstoreContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $bstoreContainer = new BstoreContainer();
        $this->object->setBstoreContainer($bstoreContainer);
        $reader = new self($bstoreContainer);
        $reader->load($recordData);
    }

    private function readBSE()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $BSE = new BSE();
        $this->object->addBSE($BSE);

        $BSE->setBLIPType($recInstance);

        $btWin32 = ord($recordData[0]);

        $btMacOS = ord($recordData[1]);

        $rgbUid = substr($recordData, 2, 16);

        $tag = Xls::getUInt2d($recordData, 18);

        $size = Xls::getInt4d($recordData, 20);

        $cRef = Xls::getInt4d($recordData, 24);

        $foDelay = Xls::getInt4d($recordData, 28);

        $unused1 = ord($recordData[32]);

        $cbName = ord($recordData[33]);

        $unused2 = ord($recordData[34]);

        $unused3 = ord($recordData[35]);

        $nameData = substr($recordData, 36, $cbName);

        $blipData = substr($recordData, 36 + $cbName);

        $reader = new self($BSE);
        $reader->load($blipData);
    }

    private function readBlipJPEG()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $pos = 0;

        $rgbUid1 = substr($recordData, 0, 16);
        $pos += 16;

        if (in_array($recInstance, [0x046B, 0x06E3])) {
            $rgbUid2 = substr($recordData, 16, 16);
            $pos += 16;
        }

        $tag = ord($recordData[$pos]);
        $pos += 1;

        $data = substr($recordData, $pos);

        $blip = new Blip();
        $blip->setData($data);

        $this->object->setBlip($blip);
    }

    private function readBlipPNG()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $pos = 0;

        $rgbUid1 = substr($recordData, 0, 16);
        $pos += 16;

        if ($recInstance == 0x06E1) {
            $rgbUid2 = substr($recordData, 16, 16);
            $pos += 16;
        }

        $tag = ord($recordData[$pos]);
        $pos += 1;

        $data = substr($recordData, $pos);

        $blip = new Blip();
        $blip->setData($data);

        $this->object->setBlip($blip);
    }

    private function readOPT()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $this->readOfficeArtRGFOPTE($recordData, $recInstance);
    }

    private function readTertiaryOPT()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readSplitMenuColors()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readDgContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $dgContainer = new DgContainer();
        $this->object->setDgContainer($dgContainer);
        $reader = new self($dgContainer);
        $escher = $reader->load($recordData);
    }

    private function readDg()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readSpgrContainer()
    {

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $spgrContainer = new SpgrContainer();

        if ($this->object instanceof DgContainer) {
            $this->object->setSpgrContainer($spgrContainer);
        } else {
            $this->object->addChild($spgrContainer);
        }

        $reader = new self($spgrContainer);
        $escher = $reader->load($recordData);
    }

    private function readSpContainer()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $spContainer = new SpContainer();
        $this->object->addChild($spContainer);

        $this->pos += 8 + $length;

        $reader = new self($spContainer);
        $escher = $reader->load($recordData);
    }

    private function readSpgr()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readSp()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readClientTextbox()
    {

        $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;

        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readClientAnchor()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;

        $c1 = Xls::getUInt2d($recordData, 2);

        $startOffsetX = Xls::getUInt2d($recordData, 4);

        $r1 = Xls::getUInt2d($recordData, 6);

        $startOffsetY = Xls::getUInt2d($recordData, 8);

        $c2 = Xls::getUInt2d($recordData, 10);

        $endOffsetX = Xls::getUInt2d($recordData, 12);

        $r2 = Xls::getUInt2d($recordData, 14);

        $endOffsetY = Xls::getUInt2d($recordData, 16);

        $this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));

        $this->object->setStartOffsetX($startOffsetX);

        $this->object->setStartOffsetY($startOffsetY);

        $this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));

        $this->object->setEndOffsetX($endOffsetX);

        $this->object->setEndOffsetY($endOffsetY);
    }

    private function readClientData()
    {
        $length = Xls::getInt4d($this->data, $this->pos + 4);
        $recordData = substr($this->data, $this->pos + 8, $length);

        $this->pos += 8 + $length;
    }

    private function readOfficeArtRGFOPTE($data, $n)
    {
        $splicedComplexData = substr($data, 6 * $n);

        for ($i = 0; $i < $n; ++$i) {
            $fopte = substr($data, 6 * $i, 6);

            $opid = Xls::getUInt2d($fopte, 0);

            $opidOpid = (0x3FFF & $opid) >> 0;

            $opidFBid = (0x4000 & $opid) >> 14;

            $opidFComplex = (0x8000 & $opid) >> 15;

            $op = Xls::getInt4d($fopte, 2);

            if ($opidFComplex) {
                $complexData = substr($splicedComplexData, 0, $op);
                $splicedComplexData = substr($splicedComplexData, $op);

                $value = $complexData;
            } else {
                $value = $op;
            }

            $this->object->setOPT($opidOpid, $value);
        }
    }
}

